#include <stdio.h>
#include <string.h>
#include "ingsoc.h"
#include "platform_api.h"
#include "ll_api.h"
#include "att_db.h"
#include "gap.h"
#include "l2cap.h"
#include "btstack_event.h"
#include "btstack_defines.h"
#include "gatt_client.h"

#include "FreeRTOS.h"
#include "timers.h"
#include "semphr.h"

#include "str_util.h"

#define INVALID_HANDLE              0xffff
#define SPSM_ID                     0x90
#define INITIAL_CREDITS             10
uint16_t            local_cid = 0xffff;
hci_con_handle_t    conn_handle = INVALID_HANDLE;


#define iprintf     platform_printf

#define CONST_MASTER    0

#ifndef APP_ROLE
#define APP_ROLE        CONST_MASTER
#endif

// GATT characteristic handles


const static uint8_t adv_data[] = {
    #include "../data/advertising.adv"
};

const static uint8_t scan_data[] = {
    #include "../data/scan_response.adv"
};

const uint8_t profile_data[] = {
    #include "../data/gatt.profile"
};

static uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset,
                                  uint8_t * buffer, uint16_t buffer_size)
{
    switch (att_handle)
    {

    default:
        return 0;
    }
}

static int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode,
                              uint16_t offset, const uint8_t *buffer, uint16_t buffer_size)
{
    switch (att_handle)
    {

    default:
        return 0;
    }
}

static void user_msg_handler(uint32_t msg_id, void *data, uint16_t size)
{
    switch (msg_id)
    {
        // add your code
    //case MY_MESSAGE_ID:
    //    break;
    default:
        ;
    }
}

const static ext_adv_set_en_t adv_sets_en[] = { {.handle = 0, .duration = 0, .max_events = 0} };

void setup_adv(void)
{
    gap_set_ext_adv_para(0,
                            CONNECTABLE_ADV_BIT | SCANNABLE_ADV_BIT | LEGACY_PDU_BIT,
                            0x00a1, 0x00a1,            // Primary_Advertising_Interval_Min, Primary_Advertising_Interval_Max
                            PRIMARY_ADV_ALL_CHANNELS,  // Primary_Advertising_Channel_Map
                            BD_ADDR_TYPE_LE_RANDOM,    // Own_Address_Type
                            BD_ADDR_TYPE_LE_PUBLIC,    // Peer_Address_Type (ignore)
                            NULL,                      // Peer_Address      (ignore)
                            ADV_FILTER_ALLOW_ALL,      // Advertising_Filter_Policy
                            0x00,                      // Advertising_Tx_Power
                            PHY_1M,                    // Primary_Advertising_PHY
                            0,                         // Secondary_Advertising_Max_Skip
                            PHY_1M,                    // Secondary_Advertising_PHY
                            0x00,                      // Advertising_SID
                            0x00);                     // Scan_Request_Notification_Enable
    gap_set_ext_adv_data(0, sizeof(adv_data), (uint8_t*)adv_data);
    gap_set_ext_scan_response_data(0, sizeof(scan_data), (uint8_t*)scan_data);
    gap_set_ext_adv_enable(1, sizeof(adv_sets_en) / sizeof(adv_sets_en[0]), adv_sets_en);
}

#define CONN_PARAM  {                   \
            .scan_int = 200,            \
            .scan_win = 100,            \
            .interval_min = 50,         \
            .interval_max = 50,         \
            .latency = 0,               \
            .supervision_timeout = 600, \
            .min_ce_len = 90,           \
            .max_ce_len = 90            \
    }

static initiating_phy_config_t phy_configs[] =
{
    {
        .phy = PHY_1M,
        .conn_param = CONN_PARAM
    },
    {
        .phy = PHY_2M,
        .conn_param = CONN_PARAM
    },
    {
        .phy = PHY_CODED,
        .conn_param = CONN_PARAM
    }
};

static const bd_addr_t rand_addr = { 0xCC, 0x11, 0xD8, 0x29, 0x81, 0x33 };

void create_conn(void)
{
    gap_ext_create_connection(INITIATING_ADVERTISER_FROM_PARAM,
                    BD_ADDR_TYPE_LE_RANDOM,           // Own_Address_Type,
                    BD_ADDR_TYPE_LE_RANDOM,           // Peer_Address_Type,
                    rand_addr,                        // Peer_Address,
                    sizeof(phy_configs) / sizeof(phy_configs[0]),
                    phy_configs);
}

static void print_string(const char *str)
{
    platform_printf("%s\n", str);
}

static void user_packet_handler(uint8_t packet_type, uint16_t channel, const uint8_t *packet, uint16_t size)
{
    uint8_t event = hci_event_packet_get_type(packet);
    const btstack_user_msg_t *p_user_msg;
    if (packet_type != HCI_EVENT_PACKET) return;

    switch (event)
    {
    case BTSTACK_EVENT_STATE:
        if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING)
            break;
        gap_set_random_device_address(rand_addr);
        gap_set_adv_set_random_addr(0, rand_addr);
#if (APP_ROLE == CONST_MASTER)
        create_conn();
#else
        setup_adv();
#endif
        break;

    case HCI_EVENT_COMMAND_COMPLETE:
        switch (hci_event_command_complete_get_command_opcode(packet))
        {
        // add your code to check command complete response
        // case :
        //     break;
        default:
            break;
        }
        break;

    case HCI_EVENT_LE_META:
        switch (hci_event_le_meta_get_subevent_code(packet))
        {
        case HCI_SUBEVENT_LE_ENHANCED_CONNECTION_COMPLETE:
            {
                const le_meta_event_enh_create_conn_complete_t * complete =
                    decode_hci_le_meta_event(packet, le_meta_event_enh_create_conn_complete_t);

                if (complete->status != 0) break;
                conn_handle = complete->handle;

                iprintf("CONNECTED\n");

#if (APP_ROLE == CONST_MASTER)
                gatt_client_is_ready(conn_handle);
#else
                att_set_db(conn_handle, profile_data);
#endif
            }
            break;
        default:
            break;
        }

        break;

    case HCI_EVENT_DISCONNECTION_COMPLETE:
        conn_handle = INVALID_HANDLE;
#if (APP_ROLE == CONST_MASTER)
        create_conn();
#else
        gap_set_ext_adv_enable(1, sizeof(adv_sets_en) / sizeof(adv_sets_en[0]), adv_sets_en);
#endif
        break;

    case ATT_EVENT_CAN_SEND_NOW:
        // add your code
        break;

    case GATT_EVENT_MTU:
        iprintf("GATT client MTU updated: %d\n", gatt_event_mtu_get_mtu(packet));
#if (APP_ROLE == CONST_MASTER)
        l2cap_create_le_credit_based_connection_request(INITIAL_CREDITS,
            SPSM_ID,
            conn_handle,
            &local_cid);
#endif
        break;

    case BTSTACK_EVENT_USER_MSG:
        p_user_msg = hci_event_packet_get_user_msg(packet);
        user_msg_handler(p_user_msg->msg_id, p_user_msg->data, p_user_msg->len);
        break;

    default:
        break;
    }
}

int check_and_add_credits(void)
{
    uint32_t local_credits, peer_credits;
    l2cap_get_le_credit_based_connection_credits(local_cid, &peer_credits, &local_credits);
    iprintf("("
            "local_credits = %u; "
            "peer_credits = %u)\n", local_credits, peer_credits);

    if (peer_credits < 5)
        l2cap_le_send_flow_control_credit(local_cid, 10);

    if (local_credits < 1)
    {
        iprintf("ERROR: run out of local_credits\n");
        return 0;
    }

    return 1;
}

void l2cap_send_timer(void)
{
    iprintf("SENDING PING");

    if (!check_and_add_credits()) return;

    l2cap_credit_based_send(local_cid, (const uint8_t *)"PING", 4);

    platform_set_timer(l2cap_send_timer, 500);
}

void l2cap_send_response(void)
{
    iprintf("SENDING PONG");

    if (!check_and_add_credits()) return;

    l2cap_credit_based_send(local_cid, (const uint8_t *)"PONG", 4);
}

static void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, const uint8_t *packet, uint16_t size)
{
    uint8_t event = hci_event_packet_get_type(packet);
    if (packet_type != HCI_EVENT_PACKET) return;

    switch (event)
    {
    case L2CAP_EVENT_CHANNEL_OPENED:
        {
            const l2cap_event_channel_opened_t *event =
                decode_l2cap_event(packet, l2cap_event_channel_opened_t);
            platform_printf("L2CAP_EVENT_CHANNEL_OPENED:\n"
                            "local_cid:     %d\n"
                            "local_credits: %d\n"
                            "peer_cid:      %d\n"
                            "peer_credits:  %d\n",
                            event->local_cid, event->local_credits,
                            event->peer_cid, event->peer_credits);
#if (APP_ROLE == CONST_MASTER)
            platform_set_timer(l2cap_send_timer, 500);
#else
            local_cid = event->local_cid;
#endif
        }
        break;
    case L2CAP_EVENT_CHANNEL_CLOSED:
        {
            const l2cap_event_channel_closed_t *event =
                decode_l2cap_event(packet, l2cap_event_channel_closed_t);
            platform_printf("L2CAP_EVENT_CHANNEL_CLOSED:\n"
                            "local_cid:     %d\n"
                            "reason:        %d\n",
                            event->local_cid, event->reason);
#if (APP_ROLE == CONST_MASTER)
            platform_set_timer(l2cap_send_timer, 0);
#endif
        }
        break;
    case L2CAP_EVENT_COMPLETED_SDU_PACKET:
        {
            const l2cap_event_complete_sdu_t *event =
                decode_l2cap_event(packet, l2cap_event_complete_sdu_t);
            platform_printf("L2CAP_EVENT_COMPLETED_SDU_PACKET:\n");
            print_hex_table(event->payload, event->total_length, print_string);
#if (APP_ROLE == CONST_MASTER)
#else
            l2cap_send_response();
#endif
        }
        break;
    case L2CAP_EVENT_FRAGMENT_SDU_PACKET:
        {
            const l2cap_event_fragment_sdu_t *event =
                decode_l2cap_event(packet, l2cap_event_fragment_sdu_t);
            platform_printf("L2CAP_EVENT_FRAGMENT_SDU_PACKET:\n"
                            "total:  %d\n"
                            "offset: %d\n",
                            event->total_length, event->offset);
            print_hex_table(event->payload, event->length, print_string);
        }
        break;
    }
}

static btstack_packet_callback_registration_t hci_event_callback_registration;

uint32_t setup_profile(void *data, void *user_data)
{
    platform_printf("setup profile\n");
    // Note: security has not been enabled.
    att_server_init(att_read_callback, att_write_callback);
    hci_event_callback_registration.callback = &user_packet_handler;
    hci_add_event_handler(&hci_event_callback_registration);
    gatt_client_register_handler(&user_packet_handler);
    att_server_register_packet_handler(&user_packet_handler);
    // For DEMO ONLY: register SPSM on both parties.
    // Formally: use GATT to negotiate SPSM
    l2cap_register_service(l2cap_packet_handler,
        SPSM_ID, 0, LEVEL_1);
    return 0;
}

